EDA

EDA#

import pandas as pd
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.graph_objs as go

from phik import phik_matrix
from numpy import log10, NaN
from seaborn import heatmap, set_theme
from json import load
from urllib.request import urlopen
from plotly.subplots import make_subplots

set_theme(style="ticks", context="talk", palette="tab10")
plt.rcParams.update({'font.size': 12})

Archivo GeoJSON para trazar los mapas más tarde

with urlopen('https://gist.githubusercontent.com/john-guerra/43c7656821069d00dcbc/raw/be6a6e239cd5b5b803c6e7c2ec405b793a9064dd/Colombia.geo.json') as response:
    counties = load(response)
df = pd.read_parquet('data_cleaned.parquet')
df_factorize = df.apply(lambda x : pd.factorize(x)[0]) 
df_corr = df_factorize.phik_matrix(interval_cols=list(df.columns)).copy()

Como podemos ver en el mapa de calor, hay una relación entre las columnas de género y grupo_etario.

fig, ax = plt.subplots(figsize=(10,7))
heatmap(df_corr, annot=True, linewidths=.6)

plt.show()
_images/4335e3ec6fff3b5917a8a02836912058d03e2a4a6b9a321026c22cadaf3b881e.png

Distribución De los Datos:

Distribución de datos factorizados: no hay una distribución común ni consistente

df_factorize.hist(figsize=(8, 10), column=['departamento', 'municipio', 'genero', 'grupo_etario','armas_medios'], 
                  grid=True, bins=20)
plt.show()
_images/3a186ec062fd7b13b312f5b2f9a98e63a37496c46f95633ca7616668b32916ce.png

Distribución de columnas de tipo datetime e integer: En la fecha, podemos ver cómo en 2020 hubo un gran aumento en los casos, hablaremos de esto más adelante en el análisis de series temporales.

En los casos, hay una gran concentración de 1 a 2 casos por fila.

df.hist(figsize=(13, 5), bins=50)
plt.show()
_images/5b08c066b793733a06e230a1b424b528bf427a6223248f0b64187ba5d945bcfa.png
df.describe(exclude='datetime')
departamento municipio armas_medios genero grupo_etario cantidad department
count 570467 570467 570467 570467 570467 570467.000000 54929
unique 32 1023 5 3 4 NaN 1
top CUNDINAMARCA BOGOTÁ D.C. (CT) ARMA BLANCA FEMENINO ADULTOS NaN SANTAFE DE BOGOTA D.C
freq 100753 54929 323262 433910 499973 NaN 54929
mean NaN NaN NaN NaN NaN 1.542531 NaN
std NaN NaN NaN NaN NaN 1.644632 NaN
min NaN NaN NaN NaN NaN 1.000000 NaN
25% NaN NaN NaN NaN NaN 1.000000 NaN
50% NaN NaN NaN NaN NaN 1.000000 NaN
75% NaN NaN NaN NaN NaN 1.000000 NaN
max NaN NaN NaN NaN NaN 19.000000 NaN

El departamento con más casos y su distribución:

table_department = df.groupby('departamento')['cantidad'].sum().reset_index()

# If you need to rename the column, you can do so
table_department = table_department.rename(columns={'cantidad': 'total_cantidad'})

# Now you can sort the DataFrame
table_department_sorted = table_department.sort_values(by='total_cantidad', ascending=False)

print(table_department_sorted)
                                         departamento  total_cantidad
14                                       CUNDINAMARCA          198440
1                                           ANTIOQUIA          118702
29                                    VALLE DEL CAUCA           93680
26                                          SANTANDER           64965
6                                              BOYACA           40437
4                                           ATLANTICO           33812
20                                               META           30026
5                                             BOLIVAR           29965
28                                             TOLIMA           27614
17                                              HUILA           26379
21                                             NARIÑO           25714
22                                 NORTE DE SANTANDER           25472
25                                          RISARALDA           23592
10                                              CAUCA           23535
19                                          MAGDALENA           16211
13                                            CORDOBA           16199
27                                              SUCRE           14102
7                                              CALDAS           12641
11                                              CESAR           12354
9                                            CASANARE            8548
8                                             CAQUETA            6840
24                                            QUINDIO            6686
18                                         LA GUAJIRA            5870
2                                              ARAUCA            4893
23                                           PUTUMAYO            4128
12                                              CHOCO            2773
0                                            AMAZONAS            1714
3   ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA...            1620
16                                           GUAVIARE            1301
15                                            GUAINIA             665
30                                             VAUPES             566
31                                            VICHADA             519
locs = table_department['departamento']
for loc in counties['features']:
    loc['id'] = loc['properties']['NOMBRE_DPT']

fig = make_subplots(
    rows=1, cols=2,
    subplot_titles=['Normal distribution', 'Logarithm 10'],
    specs=[[{"type": "mapbox"}, {"type": "mapbox"}]]
)

fig.add_trace(
    go.Choroplethmapbox(
        geojson=counties,
        locations=table_department['departamento'],
        z=table_department['total_cantidad'],
        colorbar_title='Casos',
        colorscale='YlOrRd',
        colorbar=dict(thickness=20, x=0.46),
        marker=dict(opacity=0.75)
    ),
    row=1, col=1
)

fig.add_trace(
    go.Choroplethmapbox(
        geojson=counties,
        locations=table_department['departamento'],
        z=log10(table_department['total_cantidad']),
        colorbar_title='Case count (Log10)',
        colorscale='YlOrRd',
        colorbar=dict(thickness=20, x=1.02),
        marker=dict(opacity=0.75)
    ),
    row=1, col=2
)

fig.update_layout(
    margin=dict(l=20, r=0, t=80, b=40),
    title='Casos de Violencia doméstica distribuidos en Colombia',
    mapbox1=dict(zoom=3.4, style='carto-positron', center={"lat": 4.570868, "lon": -74.2973328}),
    mapbox2=dict(zoom=3.4, style='carto-positron', center={"lat": 4.570868, "lon": -74.2973328})
)

fig.write_html('plot.html')

from IPython.display import HTML
HTML(filename='plot.html')

Los casos en todo el país están concentrados en la parte central del país, como era de esperar debido a que una gran cantidad de población se encuentra en esta región.

El lado derecho del gráfico muestra una función logarítmica con base 10 aplicada a los valores del lado izquierdo del gráfico. Esto se hace únicamente con el propósito de mejorar la representación visual de cómo están distribuidos los casos en el país, y no refleja la distribución real de los datos.

¿Qué tipo de arma es la más común?

gun_table = pd.pivot_table(df, index = 'armas_medios', values = 'cantidad', columns = None, aggfunc='count',sort=True).reset_index()
gun_table
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\4238805791.py:1: FutureWarning:

The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
armas_medios cantidad
0 ARMA BLANCA 323262
1 ARMA DE FUEGO 2710
2 ESCOPOLAMINA 3854
3 NO REPORTA 75407
4 SIN EMPLEO DE ARMAS 165234
gun_graph = px.bar(gun_table, x='armas_medios', y='cantidad', text_auto='.2s', title='Tipo de arma usada')
gun_graph.update_traces(textfont_size=12, textangle=0, textposition="outside", cliponaxis=False)
gun_graph.update_xaxes(tickvals=[0, 1, 2, 3, 4], ticktext=['ARMA BLANCA', 'ARMA DE FUEGO', 'ESCOPOLAMINA ', 'NO REPORTA', 'SIN EMPLEO DE ARMAS '])

gun_graph.write_html('gun_plot.html')
HTML(filename='gun_plot.html')

El tipo de arma más común utilizado en casos de violencia doméstica en Colombia es el arma blanca, seguida de sin empleo de armas. Es interesante ver cómo el arma blanca es mucho más utilizada que las otras, especialmente el arma de fuego y la escopolamina.

Distribución Por Género:

df.groupby('genero')['cantidad'].count()
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\706662510.py:1: FutureWarning:

The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
genero
FEMENINO      433910
MASCULINO     136031
NO REPORTA       526
Name: cantidad, dtype: int64

Se puede observar que el género Femenimo es el que más casos reporta.

dfsex = pd.pivot_table(df, index = ('genero', 'armas_medios'), values = 'cantidad', columns = None, aggfunc='count', sort=True).reset_index()
dfsex
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\2537065515.py:1: FutureWarning:

The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
genero armas_medios cantidad
0 FEMENINO ARMA BLANCA 248238
1 FEMENINO ARMA DE FUEGO 2276
2 FEMENINO ESCOPOLAMINA 2975
3 FEMENINO NO REPORTA 56193
4 FEMENINO SIN EMPLEO DE ARMAS 124228
5 MASCULINO ARMA BLANCA 74870
6 MASCULINO ARMA DE FUEGO 434
7 MASCULINO ESCOPOLAMINA 877
8 MASCULINO NO REPORTA 19106
9 MASCULINO SIN EMPLEO DE ARMAS 40744
10 NO REPORTA ARMA BLANCA 154
11 NO REPORTA ARMA DE FUEGO 0
12 NO REPORTA ESCOPOLAMINA 2
13 NO REPORTA NO REPORTA 108
14 NO REPORTA SIN EMPLEO DE ARMAS 262
dfsex['genero'].replace({'NO REPORTA': NaN}, inplace=True)
dfsex = dfsex[~dfsex['genero'].isnull()]
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\316684948.py:1: FutureWarning:

A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.

For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.



C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\316684948.py:1: FutureWarning:

The behavior of Series.replace (and DataFrame.replace) with CategoricalDtype is deprecated. In a future version, replace will only be used for cases that preserve the categories. To change the categories, use ser.cat.rename_categories instead.

Gráfico que compara cuántas víctimas hay por género, en términos del tipo de arma que se utilizó.

fig_sex = px.bar(dfsex, x='genero', y='cantidad', color='armas_medios', barmode='group', text_auto='.2s', 
                title="Género de las víctimas y el número de casos que involucran cada tipo de arma", 
                labels={'Cantidad':'Casos', 'genero':'Género y tipo de arma'}, height=400)

fig_sex.update_traces(textfont_size=12, textangle=0, textposition="outside", cliponaxis=False)

fig_sex.write_html('sex_plot.html')
HTML(filename='sex_plot.html')

Relación entre género y Grupo Etario:

df.groupby(['genero', 'grupo_etario'])['cantidad'].count()
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\758330160.py:1: FutureWarning:

The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
genero      grupo_etario
FEMENINO    ADOLESCENTES     24672
            ADULTOS         392729
            MENORES          16508
            NO REPORTA           1
MASCULINO   ADOLESCENTES     10879
            ADULTOS         107226
            MENORES          17926
            NO REPORTA           0
NO REPORTA  ADOLESCENTES         0
            ADULTOS             18
            MENORES              2
            NO REPORTA         506
Name: cantidad, dtype: int64
dfsex_age = pd.pivot_table(df, index = ('genero', 'grupo_etario'), values = 'cantidad', columns = None, aggfunc='count', sort=True).reset_index()
dfsex_age
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\1976776229.py:1: FutureWarning:

The default value of observed=False is deprecated and will change to observed=True in a future version of pandas. Specify observed=False to silence this warning and retain the current behavior
genero grupo_etario cantidad
0 FEMENINO ADOLESCENTES 24672
1 FEMENINO ADULTOS 392729
2 FEMENINO MENORES 16508
3 FEMENINO NO REPORTA 1
4 MASCULINO ADOLESCENTES 10879
5 MASCULINO ADULTOS 107226
6 MASCULINO MENORES 17926
7 MASCULINO NO REPORTA 0
8 NO REPORTA ADOLESCENTES 0
9 NO REPORTA ADULTOS 18
10 NO REPORTA MENORES 2
11 NO REPORTA NO REPORTA 506

Como se observa aquí, la mayoría de los individuos afectados son mujeres adultas, mientras que hay poca variación en el número de menores afectados entre hombres y mujeres.

fig_sex_age = px.bar(dfsex_age, x='genero', y='cantidad', color='grupo_etario', barmode='group', text_auto='.2s', 
                    title="Género de las victimas y número de casos según el tipo de arma", 
                    labels={'cantidad':'Casos', 'genero':'Género y Tipo de arma'}, height=400)

fig_sex_age.update_traces(textfont_size=12, textangle=0, textposition="outside", cliponaxis=False)

fig_sex_age.write_html('sex_age_plot.html')
HTML(filename='sex_age_plot.html')

Análisis de escopolamina:

df.head()
departamento municipio armas_medios fecha_hecho genero grupo_etario cantidad department
0 ATLANTICO BARRANQUILLA (CT) ARMA BLANCA 2010-01-01 MASCULINO ADULTOS 1 None
1 BOYACA DUITAMA ARMA BLANCA 2010-01-01 FEMENINO ADULTOS 1 None
2 CAQUETA PUERTO RICO ARMA BLANCA 2010-01-01 MASCULINO ADULTOS 1 None
3 CASANARE MANÍ ARMA BLANCA 2010-01-01 FEMENINO ADULTOS 1 None
4 CUNDINAMARCA BOGOTÁ D.C. (CT) ARMA BLANCA 2010-01-01 FEMENINO ADULTOS 1 SANTAFE DE BOGOTA D.C
dfsco = df.query('armas_medios == "ESCOPOLAMINA"')
dfsco
departamento municipio armas_medios fecha_hecho genero grupo_etario cantidad department
50040 CUNDINAMARCA BOGOTÁ D.C. (CT) ESCOPOLAMINA 2012-10-22 FEMENINO ADULTOS 1 SANTAFE DE BOGOTA D.C
50304 VALLE DEL CAUCA CALI (CT) ESCOPOLAMINA 2012-10-28 FEMENINO ADULTOS 1 None
75086 CORDOBA MONTERÍA (CT) ESCOPOLAMINA 2014-01-01 FEMENINO ADULTOS 1 None
75087 CUNDINAMARCA BOGOTÁ D.C. (CT) ESCOPOLAMINA 2014-01-01 FEMENINO ADULTOS 6 SANTAFE DE BOGOTA D.C
75088 NORTE DE SANTANDER CÚCUTA (CT) ESCOPOLAMINA 2014-01-01 FEMENINO ADULTOS 2 None
... ... ... ... ... ... ... ... ...
253836 BOLIVAR MAGANGUÉ ESCOPOLAMINA 2018-01-28 FEMENINO ADULTOS 1 None
253837 CUNDINAMARCA SOACHA ESCOPOLAMINA 2018-01-28 FEMENINO ADULTOS 2 None
253838 QUINDIO ARMENIA (CT) ESCOPOLAMINA 2018-01-28 FEMENINO ADULTOS 1 None
253839 TOLIMA IBAGUÉ (CT) ESCOPOLAMINA 2018-01-28 FEMENINO ADULTOS 1 None
254006 VALLE DEL CAUCA CALI (CT) ESCOPOLAMINA 2018-01-29 FEMENINO ADULTOS 1 None

3854 rows × 8 columns

dfdsco = pd.pivot_table(dfsco, index = 'departamento', values = 'cantidad', columns = None, aggfunc='count').reset_index()
dfdsco['departamento'].replace({'ARCHIPIELAGO DE SAN ANDRES PROVIDENCIA Y SANTA CATALINA':'SAN ANDRES'}, inplace=True)
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\223915053.py:2: FutureWarning:

A value is trying to be set on a copy of a DataFrame or Series through chained assignment using an inplace method.
The behavior will change in pandas 3.0. This inplace method will never work because the intermediate object on which we are setting values always behaves as a copy.

For example, when doing 'df[col].method(value, inplace=True)', try using 'df.method({col: value}, inplace=True)' or df[col] = df[col].method(value) instead, to perform the operation inplace on the original object.

Es cierto que el uso de escopolamina está concentrado principalmente en la región central del país, con una presencia significativa observada en Bogotá, la capital de Colombia. Esta ciudad también tiene el mayor número de casos que involucran el uso de escopolamina en incidentes de violencia doméstica, en comparación con otras regiones.

fig = px.bar(dfdsco, x='cantidad', y='departamento', text_auto='.2s', 
            title="Número de informes de violencia familiar con escopolamina por departamento", 
            labels={'departamento':'Departamento', 'cantidad':'Casos'}, orientation='h')

fig.update_traces(textfont_size=12, textangle=0, textposition="outside",cliponaxis=False)
fig.update_layout(width=1200, height=800)

fig.write_html('dsco_plot.html')
HTML(filename='dsco_plot.html')

Mapa de casos de escopolamina distribuidos en el país, con la función logaritmo base 10 aplicada para visualizar mejor su distribución.

locs = dfdsco['departamento']
for loc in counties['features']:
    loc['id'] = loc['properties']['NOMBRE_DPT']

fig = go.Figure(go.Choroplethmapbox(
    geojson=counties,
    locations=locs,
    z=log10(dfdsco['cantidad']),
    colorscale='YlOrRd',
    colorbar_title="Número"
))

fig.update_layout(
    mapbox_style="carto-positron",
    mapbox_zoom=3.3,
    mapbox_center={"lat": 4.570868, "lon": -74.2973328},
    title='Casos de escopolamina distribuidos en el país Log10'
)

fig.write_html('dsco_map_plot.html')
HTML(filename='dsco_map_plot.html')

Análisis de Series de Tiempo:

df['day'] = df['fecha_hecho'].dt.day_name()
df['month'] = df['fecha_hecho'].dt.month_name()
df.head()
departamento municipio armas_medios fecha_hecho genero grupo_etario cantidad department day month
0 ATLANTICO BARRANQUILLA (CT) ARMA BLANCA 2010-01-01 MASCULINO ADULTOS 1 None Friday January
1 BOYACA DUITAMA ARMA BLANCA 2010-01-01 FEMENINO ADULTOS 1 None Friday January
2 CAQUETA PUERTO RICO ARMA BLANCA 2010-01-01 MASCULINO ADULTOS 1 None Friday January
3 CASANARE MANÍ ARMA BLANCA 2010-01-01 FEMENINO ADULTOS 1 None Friday January
4 CUNDINAMARCA BOGOTÁ D.C. (CT) ARMA BLANCA 2010-01-01 FEMENINO ADULTOS 1 SANTAFE DE BOGOTA D.C Friday January
new_order_month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
new_order_day = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']

df_day = df.groupby('day')['cantidad'].sum().reindex(new_order_day, axis=0).reset_index()
df_month = df.groupby('month')['cantidad'].sum().reindex(new_order_month, axis=0).reset_index()
fig, (ax1, ax2) = plt.subplots(2, figsize=(10,16))

for i, (month, cases) in enumerate(zip(df_month['month'], df_month['cantidad'])):
    ax1.bar(month, cases)
    ax1.annotate(str(cases), xy=(month, cases), ha='center', va='bottom')

ax1.set_xlabel('Meses')
ax1.set_ylabel('Casos')
ax1.set_title('Casos por mes')
ax1.set_xticklabels(df_month['month'], rotation=25)
ax1.set_xticks(range(len(df_month['month'])))

for i, (day, cases) in enumerate(zip(df_day['day'], df_day['cantidad'])):
    ax2.bar(day, cases)
    ax2.annotate(str(cases), xy=(day, cases), ha='center', va='bottom')

ax2.set_xlabel('Días de la semana')
ax2.set_ylabel('Casos')
ax2.set_title('Casos por día de la semana')
ax2.set_xticklabels(df_day['day'], rotation=25)
ax2.set_xticks(range(len(df_day['day'])))

plt.tight_layout()
plt.savefig('total_cases_month_and_day_with_labels.jpg')
plt.show()
C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\2426525920.py:10: UserWarning:

set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.

C:\Users\Andres\AppData\Local\Temp\ipykernel_28564\2426525920.py:20: UserWarning:

set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
_images/18abc10bc2148d84674f8e8e2a7ae82af429fa0becbbd290e3fd7d712acd20d0.png

Podemos observar cómo los casos por mes tienen una tendencia estable, excepto en diciembre, este mes tiene una disminución abrupta que puede deberse a las festividades celebradas durante esta época del año.

Asimismo, los domingos, que es el día de descanso en Colombia, es el día con el mayor número de casos de la semana.

df_date1 = df.groupby('fecha_hecho')['cantidad'].sum().reset_index()

df_date2 = df.groupby(df.fecha_hecho.dt.year)['cantidad'].sum().reset_index()
df_date2 = df_date2.rename({'fecha_hecho':'year'}, axis=1)
fig, (ax1, ax2, ax3) = plt.subplots(3, figsize=(20,16))


ax1.plot(df_date1['fecha_hecho'], df_date1['cantidad'], marker='o', color='b')
ax1.set_xlabel('Fecha')
ax1.set_ylabel('Casos')
ax1.set_title('Timeline de casos diarios')
ax1.grid(True)

ax2.bar(df_date2['year'], df_date2['cantidad'], color='g')
ax2.set_xlabel('Años')
ax2.set_ylabel('Casos')
ax2.set_title('Total de casos agrupados por año')
ax2.grid(True)

df_date1_filtered = df_date1[df_date1['fecha_hecho'] > '2019-01-01']
ax3.plot(df_date1_filtered['fecha_hecho'], df_date1_filtered['cantidad'], marker='s', color='r')
ax3.set_xlabel('Fecha')
ax3.set_ylabel('Casos')
ax3.set_title('Distribución de casos diarios desde 2019 hasta 2022')
ax3.grid(True)

plt.tight_layout()

plt.savefig('timeline_graph.jpg')
plt.show()
_images/e6a79104edab4668f5c6a83c3d312da648e23d0d36194b302ec4ea8e2333410b.png

Se puede concluir a partir de los gráficos anteriores que cada fin de diciembre y principio de enero, durante las vacaciones en Colombia, hay un aumento en los casos reportados. Esto ocurre todos los años en todo el país.